package algorithms;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import register.Register;
import gates.Factory;
import gui.UserInterface;

/**
 * Class defining Grovers Circuit, the GUI to display the circuit results and all gates contained within the circuit.
 * Main role is handling the UI.
 * @author Matt Cole, Alex Bush 
 */
public class GroverCircuit implements ActionListener {
	/**Minimum number of allowed qubits*/
	private final int minNumQubits = 1;
	/**Maximum number of allowed qubits, as limited by classical architecture*/
	private final int maxNumQubits = 16;
	/**Store the UI object to give the gui*/
	private UserInterface UI;
	/**number of qubits in register*/
	protected int numQubits;
	/**Quantum register*/
	protected Register reg;
	/**number of iterations*/
	protected int iteration;	
	/**element being searched for*/
	protected int searchElem;
	/**boolean to determine whether or not the vector plot needs updating*/
	protected boolean updateVectorPlot;
	/**optimal number of grover iterations. This is based on equations found in Quantum 
	 * Computing and Quantum Information */
	private int optimal;
	/**number of states in the register*/
	private double numStates;
	/**message giving the optimal number of iterations*/
	private String optimalString;

	/** Stores the number of the qubit to be prepared. If this is less than the number of qubits in the
	 * register then the nextStep() method will prepare the resister accordingly rather than applying the next
	 * iteration of the algorithm.
	 */
	 protected int qubitPrepare;					

	 public GroverCircuit() {
		 iteration = 0;
		 numQubits = 0;
		 qubitPrepare = 0;	
		 updateVectorPlot = false;
	 }

	 /**
	  * Initiate the program.
	  */
	 public void runAlgorithm() {
		 // Create the UI. Pass "this" so that Circuit can be added as an action
		 // listener to the buttons on the interface
		 UI = new UserInterface(this);
		 UI.showIntro();
	 }

	 /**
	  * Controls what the program does when a button is clicked.
	  */
	 public void actionPerformed(ActionEvent event) {

		 // Next button on intro window. The following code simply retrieves the user
		 // input and checks its validity
		 if(event.getActionCommand().equals("next")) {
			 numQubits = 0;
			 boolean validRegSize = false; 
			 boolean validSearchElem = false;
			 boolean correctDataType = false;

			 // Check that the inputs are integers
			 try {
				 numQubits = Integer.parseInt(UI.getNumQubits());
				 searchElem = Integer.parseInt(UI.getSearchElem());
				 correctDataType = true;
			 }
			 catch(NumberFormatException e) {
				 UI.inputError("Enter integers only.");
				 UI.showIntro();
			 }

			 // Check that the numQubits is acceptable
			 if(correctDataType) {
				 if(numQubits >= minNumQubits && numQubits <= maxNumQubits) {
					 validRegSize = true;
				 }
				 else {
					 validRegSize = false;
					 UI.inputError("Enter a register size between "
							 + minNumQubits + " and " + maxNumQubits + ".");
					 UI.showIntro();
				 }

				 // Check that the searchElem size is acceptable		
				 if(validRegSize) {
					 int maxSearchElem = (int)Math.pow(2,numQubits)-1;
					 if(searchElem >= 0 && searchElem <= maxSearchElem) {
						 validSearchElem = true;
					 }
					 else {
						 validSearchElem = false;
						 UI.inputError("Enter a search element between 1 and "
								 + maxSearchElem + ".");
						 UI.showIntro();
					 }

					 // If all tests are passed, proceed to the next step by displaying the main window
					 if(validRegSize && validSearchElem) {
						 reg = new Register(numQubits);
						 numStates = Math.pow(2,numQubits);
						 double optimalDoub = Math.acos(Math.sqrt(1.0/numStates))/  (Math.asin(2*Math.sqrt(numStates-1)/numStates));
						 if((optimalDoub*2)%2==1){
							 optimal=(int)Math.floor(optimalDoub);
						 }
						 else{
							 optimal = Math.round((float)optimalDoub);
						 }
						 optimalString = "The optimal number of Grover iterations is " + optimal + ".\n";
						 UI.showMain(reg, searchElem);
						 UI.enableMeasureButton();
						 UI.printMessage(getName() + " with a " + numQubits + " qubit register." + "\n"+
								 " The search element is " + searchElem + ".\n" +
								 optimalString);
					 }

				 }
			 }
		 }

		 // Exit button closes the program
		 else if(event.getActionCommand().equals("exit")) {
			 System.exit(0);
		 }

		 // Next step button on main window calls the next step method
		 // and updates the screen
		 else if(event.getActionCommand().equals("next_step")) {
			 // Performs the next step of the algorithm, storing the action taken as a string
			 String action = nextStep();
			 // Pass the newly altered register along with the action taken to the UI
			 UI.update(reg, action, updateVectorPlot);
		 }

		 // Measure button on the main window
		 else if(event.getActionCommand().equals("measure")) {
			 String result = measureRegister();
			 UI.update(reg, result, false);
			 UI.disableStepButtons();
		 }

		 // Reset button on the main window
		 else if(event.getActionCommand().equals("reset")) {
			 iteration = 0;
			 qubitPrepare = 0;
			 reg.setToGroundState();
			 UI.showMain(reg, searchElem);
			 UI.printMessage("Reset " + getName() + " with a " + numQubits + " qubit register." +
					 " The search element is " + searchElem + ".\n" + optimalString);
			 UI.enableMeasureButton();
		 }

		 // New simulatoin button on the main window
		 else if(event.getActionCommand().equals("new")) {
			 iteration = 0;
			 qubitPrepare = 0;
			 UI.showIntro();
		 }

	 }

	 /**
	  * Provide the next step of the iteration.
	  * Initially applies <code>numQubits</code> number of Hadamard gates, one for each qubit,
	  * to prepare the register. This will not increment the counter.
	  * Then performs as many iteration steps as the user wants.
	  * @return String to print to the UI.
	  */
	 public String nextStep() {
		 if (qubitPrepare < numQubits){

			 // The vector panel does not need to be updated until the last preparation
			 updateVectorPlot = false;

			 // If this is the last preparation then the vector panel will need to be updated
			 if (qubitPrepare == numQubits-1){
				 updateVectorPlot = true;
			 }

			 // Prepare the register for the current prepare qubit
			 String action = prepareRegister();
			 qubitPrepare++;
			 return action;
		 }
		 else {

			 // The vector plot will be updated next time the UI is updated.
			 updateVectorPlot = true;
			 return performIteration();

		 }
	 }

	 /**
	  * Measures the register returning information about the measurement. 
	  * Will only make measurement if the register has been prepared.
	  * @return Result, number of iterations taken and expected result. To be printed to the UI.
	  */
	 public String measureRegister() {

		 // Reset the vector diagram as no long valid
		 UI.removeRegStateVector();

		 // Apply the measurement to the register
		 Factory.createMeasurement().apply(reg);
		 int result = reg.postMeasure();
		 String output = getName() + " returns a result of " + result + " after " + iteration +
		 " iteration(s). The expected result is " + searchElem + ".";
		 if(qubitPrepare<numQubits){
			 output += "   WARNING: Register was not fully prepared";
		 }
		 return output;
	 }


	 /**
	  * Applies the algorithm to the register.
	  * @return String to print to the UI.
	  */
	 public String performIteration() {
		 Factory.createGroverIteration(searchElem, numQubits).apply(reg);
		 iteration++;
		 return "Performed Grover iteration "+iteration+" on the register.";
	 }

	 /**
	  * Prepares the specified qubit of the register.
	  * @return String to print to the UI.
	  */
	 public String prepareRegister() {

		 // Perform the preparation
		 Factory.createHadamard(qubitPrepare).apply(reg);
		 String action = "Prepared qubit " + qubitPrepare + " by applying a Hadamard gate with target bit "
		 + qubitPrepare + " to the register.";

		 // If the register is now fully prepared, measurements may now be taken
		 if (qubitPrepare == numQubits-1){
			 action += " The register is now fully prepared.";
		 }

		 // Return action taken as a string
		 return action;

	 }

	 /**
	  * Return the name of the current algorithm.
	  * @return Name of algorithm.
	  */
	 public String getName() {
		 return "Grovers Algorithm";
	 }

	 /**
	  * Return the minimum allowed number of qubits
	  * @return minNumQubits
	  */
	 public int getMinNumQubits() {
		 return minNumQubits;
	 }

	 /**
	  * Return the maximum allowed number of qubits
	  * @return maxNumQubits
	  */
	 public int getMaxNumQubits() {
		 return maxNumQubits;
	 }

}
